package org.cytoscape.cytargetlinker.internal;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.cytoscape.application.CyApplicationManager;
import org.cytoscape.application.swing.CySwingApplication;
import org.cytoscape.cytargetlinker.internal.dialog.CyTargetLinkerDialog;
import org.cytoscape.cytargetlinker.internal.dialog.ResultTab;
import org.cytoscape.cytargetlinker.internal.model.Edge;
import org.cytoscape.cytargetlinker.internal.model.Node;
import org.cytoscape.cytargetlinker.internal.model.RegNet;
import org.cytoscape.cytargetlinker.internal.util.Preferences;
import org.cytoscape.cytargetlinker.internal.view.CTLVisualStyle;
import org.cytoscape.event.CyEventHelper;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNetworkFactory;
import org.cytoscape.model.CyNetworkManager;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.CyTable;
import org.cytoscape.model.subnetwork.CyRootNetworkManager;
import org.cytoscape.property.PropertyUpdatedEvent;
import org.cytoscape.property.PropertyUpdatedListener;
import org.cytoscape.service.util.CyServiceRegistrar;
import org.cytoscape.util.swing.FileUtil;
import org.cytoscape.view.layout.CyLayoutAlgorithmManager;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.model.CyNetworkViewFactory;
import org.cytoscape.view.model.CyNetworkViewManager;
import org.cytoscape.view.vizmap.VisualMappingFunctionFactory;
import org.cytoscape.view.vizmap.VisualMappingManager;
import org.cytoscape.view.vizmap.VisualStyle;
import org.cytoscape.view.vizmap.VisualStyleFactory;
import org.cytoscape.work.TaskFactory;
import org.cytoscape.work.TaskIterator;
import org.cytoscape.work.TaskManager;
import org.cytoscape.work.swing.DialogTaskManager;
import org.osgi.framework.BundleContext;



public class CTLManager implements PropertyUpdatedListener {
	
	private CyApplicationManager cyApplicationManager;
//	private CyNetworkViewManager netViewMgr;
	private CyNetworkManager netMgr;
//	private CyNetworkFactory netFactory;
//	private TaskManager<?, ?> taskMgr;
	private CyNetworkViewFactory netViewFactory;
//	private CyRootNetworkManager rootNetworkMgr;
	private CySwingApplication swingApp;
//	private CyServiceRegistrar serviceRegistrar;
	private VisualStyleFactory visualStyleFactory;
	private VisualMappingManager visualMappingMgr;
	private VisualMappingFunctionFactory discreteMappingFactory;
	private VisualMappingFunctionFactory continuousMappingFactory;
	private VisualMappingFunctionFactory passThroughMappingFactory;
//	private FileUtil fileUtil;
//	private CyEventHelper eventHelper;
//	private CyLayoutAlgorithmManager layoutMgr;
	private DialogTaskManager dialogTaskManager;
	private TaskFactory taskFactory;
	
	private BundleContext context;
	
	public enum Direction {
		SOURCE,
		TARGET,
		BOTH
	}
	
	public CTLManager(CyApplicationManager cyApplicationManager,
			CyNetworkViewManager netViewMgr, CyNetworkManager netMgr, CyNetworkFactory netFactory,
			TaskManager<?, ?> taskMgr, CyNetworkViewFactory netViewFactory,
			CyRootNetworkManager rootNetworkMgr, CySwingApplication swingApp,
			CyServiceRegistrar serviceRegistrar,
			VisualStyleFactory visualStyleFactory,
			VisualMappingManager visualMappingMgr,
			VisualMappingFunctionFactory discreteMappingFactory,
			VisualMappingFunctionFactory continuousMappingFactory,
			VisualMappingFunctionFactory passThroughMappingFactory,
			FileUtil fileUtil, CyEventHelper eventHelper, CyLayoutAlgorithmManager layoutMgr,
			DialogTaskManager dialogTaskManager,
			TaskFactory taskFactory, BundleContext context, ResultTab tab) {
		super();
		this.cyApplicationManager = cyApplicationManager;
//		this.netViewMgr = netViewMgr;
		this.netMgr = netMgr;
//		this.netFactory = netFactory;
//		this.taskMgr = taskMgr;
		this.netViewFactory = netViewFactory;
//		this.rootNetworkMgr = rootNetworkMgr;
		this.swingApp = swingApp;
//		this.serviceRegistrar = serviceRegistrar;
		this.visualStyleFactory = visualStyleFactory;
		this.visualMappingMgr = visualMappingMgr;
		this.discreteMappingFactory = discreteMappingFactory;
		this.continuousMappingFactory = continuousMappingFactory;
		this.passThroughMappingFactory = passThroughMappingFactory;
//		this.fileUtil = fileUtil;
//		this.eventHelper = eventHelper;
//		this.layoutMgr = layoutMgr;
		this.dialogTaskManager = dialogTaskManager;
		this.taskFactory = taskFactory;
		this.context = context;
		
		
		this.tab = tab;
		userNetworkIds = new HashMap<String, CyNode>();
		rins = new ArrayList<RegNet>();
		newNodes = new HashMap<String, CyNode>();
	}

	private File networkDir;
	// network provided by the user
	private CyNetwork userNetwork;
	// identifierAttribute, node-identifier
	private Map<String, CyNode> userNetworkIds;
	// node attribute that contains the identifier in the user network
	private String identifierAttribute;
	
	// list of regulatory interaction networks
	private List<RegNet> rins;
	private String statusMessage = "";
	
	// resulting network 
	// TODO: allow the user to decide if a new network should be created or not
	private CyNetwork resultingNetwork;
	// name of the new network
	private String resultingNetworkName;
	// nodes that will be added to the network
	private Map<String, CyNode> newNodes;
	// visual style of the new network
	// TODO: different color of nodes for different edge types (e.g. miRNA and transcription factors)
	private VisualStyle visualStyle;
	
	private ResultTab tab;
		
	// tab that contains the result information
//	private ResultTab tab;
	// decides if the nodes in the user network should only be targets or sources or both
	private Direction direction = Direction.BOTH;
	
	private int threshold = 1;


	/**
	 * opens the load interactions dialog
	 */
	public void openDialog() {
		userNetworkIds.clear();
		rins.clear();
		CyTargetLinkerDialog dialog = new CyTargetLinkerDialog(this, cyApplicationManager, swingApp);
//		dialog.showDialog();
		dialog.setVisible(true);
	}
	
	/**
	 * starts the merge task (add interactions from the 
	 * RINs into the resulting network)
	 * @param rins
	 */
	public void startMerge(List<File> files) {
		System.out.println("START MERGE");
		MergeTask task = new MergeTask(this, files);
		
		TaskIterator iterator = taskFactory.createTaskIterator();
		iterator.append(task);
		dialogTaskManager.execute(iterator);
		
//		JTaskConfig jTaskConfig = new JTaskConfig();
//		jTaskConfig.setOwner(Cytoscape.getDesktop());
//		jTaskConfig.displayCloseButton(true);
//		jTaskConfig.displayCancelButton(true);
//		jTaskConfig.displayStatus(true);
//		jTaskConfig.setAutoDispose(false);
		
		// Execute Task in New Thread; pops open JTask Dialog Box.
//		TaskManager.executeTask(task, jTaskConfig);
		
//		Cytoscape.getDesktop().getCytoPanel(SwingConstants.EAST).setState(CytoPanelState.DOCK);
	}
	
	/**
	 * adds regulatory interactions to the resulting nMartina KutmonHomeetwork
	 */
	public void mergeNetworks() {		
		checkNetName();
		
		//TODO: copy user network into new network
		resultingNetwork = userNetwork;
		
		// add a new attribute for the network nodes in the new network
		addAttributeToPanel(resultingNetwork.getNodeList());

		CyTable nodeTable = resultingNetwork.getDefaultNodeTable();
		CyTable edgeTable = resultingNetwork.getDefaultEdgeTable();
		
		// adds new nodes and edges to the resulting network
		addNewEdgesToNetwork(nodeTable, edgeTable);

		// apply treshhold = 1
		RINHandler.getHandler().applyThreshold(this);
		
		// apply force directed layout
		applyLayout();
		
		// creates CyTargetLinker visual style
		applyVisualStyle();
	}
	
	public void applyVisualStyle() {
		if(visualStyle == null) {
			CTLVisualStyle style = new CTLVisualStyle(this, visualStyleFactory, visualMappingMgr, discreteMappingFactory, continuousMappingFactory, passThroughMappingFactory);
			visualStyle = style.getVisualStyle();
		}
		CyNetworkView view = netViewFactory.createNetworkView(resultingNetwork);
		visualStyle.apply(view);
		view.updateView();
	}
	
	public void applyLayout() {
//		int count = 0;
//		for(RegNet net : rins) {
//			count = count + net.getNewInteractions();
//		}
//		
//		count = count - RINHandler.getHandler().getRemovedNodes().size();
//		if(count < 10000) {
//			CyLayoutAlgorithm alg = layoutMgr.getLayout("force-directed");
//			alg.
//			CyLayoutAlgorithm alg = CyLayouts.getLayout("force-directed");
//			alg.doLayout();
//		}
	}
	
	/**
	 * adds all new edges and nodes for all RINs in the resulting network
	 */
	private void addNewEdgesToNetwork(CyTable nodeTable, CyTable edgeTable) {
		for(RegNet network : rins) {
			for(Edge edge : network.getEdges()) {
				Node source = edge.getSource();
				Node target = edge.getTarget();
			
				CyNode cySource = null;
				CyNode cyTarget = null;
				if(direction == Direction.BOTH || direction == Direction.SOURCE) {
					cySource = findMatchingCyNode(source);
					cyTarget = createNewNode(target, nodeTable, network);
					createEgde(cySource, cyTarget, edge, network, edgeTable);
				}
				if(direction == Direction.BOTH || direction == Direction.TARGET) {
					cySource = createNewNode(source, nodeTable, network);
					cyTarget = findMatchingCyNode(target);
					createEgde(cySource, cyTarget, edge, network, edgeTable);
				} 
			}
		}
	}
	
	/**
	 * creates a new edge in the resulting network
	 */
	private void createEgde(CyNode source, CyNode target, Edge edge, 
			RegNet network, CyTable edgeTable) {
		if(source != null && target != null) {
			edge.setCySource(source);
			edge.setCyTarget(target);
			
			CyEdge cyEdge = resultingNetwork.addEdge(source, target, true);
			network.getCyEdges().add(cyEdge);
			
			for(String str : edge.getProperties().keySet()) {
				registerColumn(str, String.class, edgeTable);
				edgeTable.getRow(cyEdge.getSUID()).set(str, edge.getProperties().get(str));
			}
			registerColumn("interaction", String.class, edgeTable);
			edgeTable.getRow(cyEdge.getSUID()).set("interaction", network.getName());
		}
	}
	
	/**
	 * checks if the user network does not contain a node
	 * with one of the identifiers specified
	 */
	private CyNode findMatchingCyNode(Node node) {
		for(String id : node.getIdentifiers()) {
			if(userNetworkIds.containsKey(id)) {
				return userNetworkIds.get(id);
			}
		}
		return null;
	}
	
	/**
	 * creates a new node if the node is not present in the user network
	 */
	private CyNode createNewNode(Node node, CyTable nodeTable, RegNet network) {
		CyNode cyNode = null;

		String id = node.getId();
		for(String str : node.getIdentifiers()) {
			if(userNetworkIds.containsKey(str)) {
				id = str;
				break;
			}
		}

		if(!userNetworkIds.containsKey(id)) {
			for(String str : node.getIdentifiers()) {
				if(newNodes.containsKey(str)) {
					network.getCyNodes().add(newNodes.get(str));
					return newNodes.get(str);
				}
			}
			
			cyNode = resultingNetwork.addNode();
			if(node.getIdentifiers().size() > 1) {
				registerColumn("identifiers", List.class, nodeTable);
				nodeTable.getRow(cyNode.getSUID()).set("identifiers", node.getIdentifiers());
			}
			for(String str : node.getProperties().keySet()) {
				registerColumn(str, String.class, nodeTable);
				nodeTable.getRow(cyNode.getSUID()).set(str, node.getProperties().get(str));
			}
			
			nodeTable.getRow(cyNode.getSUID()).set("label", getLabel(cyNode, nodeTable));
			nodeTable.getRow(cyNode.getSUID()).set(
					Preferences.getInstance().getNodeTypeAttribute(),
					Preferences.getInstance().getNewNodeValue());
			
			network.getCyNodes().add(cyNode);
			
			for(String str : node.getIdentifiers()) {
				newNodes.put(str, cyNode);
			}
		} else {
			cyNode = userNetworkIds.get(id);
		}
		
		return cyNode;
	}
	
	private void addAttributeToPanel(List<CyNode> nodeList) {
		CyTable nodeTable = resultingNetwork.getDefaultNodeTable();
		
		// create a column for the table
		registerColumn(Preferences.getInstance().getNodeTypeAttribute(), String.class, nodeTable);
		registerColumn("label", String.class, nodeTable);
		
		// adding the new attribute to the User node(s).....
		for (CyNode node : nodeList) {
			// check if node has value in attribute NodeType
			// TODO: check if that works
			CyRow row = nodeTable.getRow(node.getSUID());
			String nodeType = row.get(Preferences.getInstance().getNodeTypeAttribute(), String.class);
			
			if(nodeType == null || nodeType.equals("")) {
				// if not = set value to NetworkNode
				if(isNetworkNode(node, nodeTable)) {					
					row.set(Preferences.getInstance().getNodeTypeAttribute(), Preferences.getInstance().getNetworkNodeValue());
					row.set("label", getLabel(node, nodeTable));
				} else {
					row.set(Preferences.getInstance().getNodeTypeAttribute(), Preferences.getInstance().getGroupNodeValue());
				}
			} else {
				// if value is not NewNode - set value to NetworkNode
				if(!nodeType.equals(Preferences.getInstance().getNewNodeValue())) {
					if(isNetworkNode(node, nodeTable)) {
						row.set(Preferences.getInstance().getNodeTypeAttribute(), Preferences.getInstance().getNetworkNodeValue());
						row.set("label", getLabel(node, nodeTable));
					} else {
						row.set(Preferences.getInstance().getNodeTypeAttribute(), Preferences.getInstance().getGroupNodeValue());
					}
				}
			}
		}
			
//		Cytoscape.firePropertyChange(Cytoscape.ATTRIBUTES_CHANGED, null, null);
	}
	
	private void registerColumn(String name, Class<?> c, CyTable table) {
		if(table.getColumn(name) == null) {
			if(c == List.class) {
				table.createListColumn(name, String.class, false);
			} else {
				table.createColumn(name, c, false);
			}
		}
	}
	
	/**
	 * if a node has an attribute "label" or "Label"
	 * it is used to label the node
	 * otherwise the user would see the often unreadable identifiers
	 */
	private String getLabel(CyNode node, CyTable nodeTable) {
		CyRow row = nodeTable.getRow(node.getSUID());
		String label = row.get("label", String.class);
		
		if(label == null) {
			label = row.get("Label", String.class);
			if(label == null) {
				label = row.get("canonicalName", String.class);
				if(label == null || label.equals("")) {
					label = node.getSUID().toString();
				}
			}
		}
		return label;
	}
	
	/**
	 * checks if a node exists in the user network
	 */
	private boolean isNetworkNode(CyNode node, CyTable nodeTable) {
		String nodeTypeAttribute = nodeTable.getRow(node.getSUID())
				.get(Preferences.getInstance().getNodeTypeAttribute(), String.class);
		
		if(nodeTypeAttribute == null) {
			if(!isGroupNode(node, nodeTable)) {
				return true;
			}
		} else {
			// if value is not NewNode - set value to NetworkNode
			if(!nodeTypeAttribute.equals(Preferences.getInstance().getNewNodeValue())) {
				if(!isGroupNode(node, nodeTable)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * checks if a node is a GPML group node
	 */
	private boolean isGroupNode(CyNode node, CyTable nodeTable) {
		String groupStyle = nodeTable.getRow(node.getSUID()).get("GroupStyle", String.class);
		
		if(groupStyle != null && (groupStyle.equals("Group") || groupStyle.equals("Complex"))) {
			return true;
		}
		String anchorStyle = nodeTable.getRow(node.getSUID()).get("StartLineType", String.class);
		if(anchorStyle != null) {
			return true;
		}
		return false;
	}

	/**
	 * checks if network name is already given
	 */
	private void checkNetName() {
		Set<CyNetwork> networks = netMgr.getNetworkSet();
		for(CyNetwork net : networks) {
			String name = net.getDefaultNetworkTable().getRow
					(net.getSUID()).get("name", String.class);
//			List<String> list = net.getDefaultNetworkTable().getColumn("name").getValues(String.class);
//			
//			String name = "";
//			if(!list.isEmpty()) {
//				name = list.get(0);
//			}

			// TODO: check if this works!
			if(name.equals(resultingNetworkName)) {
				setResultingNetworkName(resultingNetworkName + "_1");
				checkNetName();
			}
		}
	}
	
	/**
	 * saves all node identifiers from the user network in the
	 * userIdMultiMap
	 */
	public void findUserNetworkIdentifiers() {
		for (CyNode node : userNetwork.getNodeList()) {
//			Set<String> values = new HashSet<String>();
			String id = userNetwork.getDefaultNodeTable().getRow(node.getSUID()).get(identifierAttribute, String.class);
//			String row = userNetwork.getRow(node).get(identifierAttribute,String.class);
//			if(row == null || row.equals("") ) {
////				@SuppressWarnings("unchecked")
////				List<String> ids = userNetwork.getRow(node).get(identifierAttribute,List.class);
////				if(ids != null) values.addAll(ids);
//			} else {
//				values.add(row);
//			}
			
//			for(String id : values) {
				if(id != null && !"".equals(id)) {
					userNetworkIds.put(id, node);
				}
//			}
		}
	}
	
	// SETTERS & GETTERS
	
	public List<RegNet> getRins() {
		return rins;
	}

	public File getNetworkDir() {
		return networkDir;
	}

	public void setNetworkDir(File networkDir) {
		this.networkDir = networkDir;
	}

	public CyNetwork getUserNetwork() {
		return userNetwork;
	}

	public void setUserNetwork(CyNetwork network, String idAttribute) {
		if(network != null && idAttribute != null && !idAttribute.equals("")) {
			userNetwork = network;
			identifierAttribute = idAttribute;
		}
	}

	public CyNetwork getResultingNetwork() {
		return resultingNetwork;
	}

	public void setResultingNetwork(CyNetwork resultingNetwork) {
		this.resultingNetwork = resultingNetwork;
	}

	public String getResultingNetworkName() {
		return resultingNetworkName;
	}

	public void setResultingNetworkName(String resultingNetworkName) {
		this.resultingNetworkName = resultingNetworkName;
	}

	public Direction getDirection() {
		return direction;
	}

	public void setDirection(Direction direction) {
		this.direction = direction;
	}

	public Map<String, CyNode> getUserNetworkIds() {
		return userNetworkIds;
	}

	public void setUserNetworkIds(Map<String, CyNode> userNetworkIds) {
		this.userNetworkIds = userNetworkIds;
	}

	public String getStatusMessage() {
		return statusMessage;
	}

	public void setStatusMessage(String statusMessage) {
		this.statusMessage = statusMessage;
	}

	public VisualStyle getVisualStyle() {
		return visualStyle;
	}

	public int getThreshold() {
		return threshold;
	}

	public void setThreshold(int threshold) {
		this.threshold = threshold;
	}

	public Map<String, CyNode> getNewNodes() {
		return newNodes;
	}

	public void setNewNodes(Map<String, CyNode> newNodes) {
		this.newNodes = newNodes;
	}

	@Override
	public void handleEvent(PropertyUpdatedEvent event) {
	
//		if (event.getPropertyName().equalsIgnoreCase(Cytoscape.NETWORK_DESTROYED)) {
//			if(resultingNetwork != null) {
//				String id = resultingNetwork.getIdentifier();
//				if(event.getNewValue().equals(id)) {
//					tab.removeTab();
//				}
//			}
//		}
	}

	public BundleContext getContext() {
		return context;
	}

	public CyNetworkViewFactory getNetViewFactory() {
		return netViewFactory;
	}

	public ResultTab getTab() {
		return tab;
	}

	public CyNetworkManager getNetMgr() {
		return netMgr;
	}
	
}
